package fauxman.DynamicElements;

import java.awt.Graphics;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedList;

import fauxman.Enums.GameElementName;
import fauxman.Enums.StateType;
import fauxman.StaticElements.PlayableArea;
import fauxman.interfaces.DynamicElement;
import fauxman.interfaces.MoveableElement;
import fauxman.model.WorldModel;
import fauxman.states.EatenGhostState;
import fauxman.states.GhostState;
import fauxman.states.LeavingHouseState;
import fauxman.states.PurgatoryDownState;
import fauxman.states.PurgatoryUpState;
import fauxman.states.VulnerableGhostState;
import fauxman.utilities.ActionUtil;

public abstract class Ghost extends MoveableElement{

	protected LinkedList<GhostState> states; // The size of states is from 1 - 2.  Vulnerable will always be last.  If size 2 then vulnerable will be one of those and is inferior on its actions. 
	protected BufferedImage appearance;
	private Point currentIntersection; //TODO: change to int [2];
	private int reversalBlock[] = {-1, -1};
	private WorldModel model;
	private ArrayList<GhostState> nonReversalStates;
	private int inHouseStateChanges;
	private boolean leavingTimer;

	public Ghost(int conX, int conY, int disX, int disY, int width, int height, WorldModel model){ 
		this.conX = conX;
		this.conY = conY;
		this.disX = disX;
		this.disY = disY;
		this.width = width;
		this.height = height;
		this.conHitbox = new Rectangle(conX+(width/8)*3, conY+(height/8)*4, width/8, height/8);	// Change continuous hitbox to 1 X 1 at (4,4) of a pixel tile.
		states = new LinkedList<GhostState>();
		this.currentIntersection = new Point(-1,-1);
		this.gNum = GameElementName.GHOST;
		this.model = model;
		inHouseStateChanges = 0; 
		initNonReversalStates();
		this.leavingTimer = false;
		//changeState(getChaseState());
	}

	private void initNonReversalStates() {
		nonReversalStates = new ArrayList<GhostState>();
		nonReversalStates.add(VulnerableGhostState.getInstance());
		nonReversalStates.add(EatenGhostState.getInstance());
		nonReversalStates.add(LeavingHouseState.getInstance());
		nonReversalStates.add(PurgatoryUpState.getInstance());
		nonReversalStates.add(PurgatoryDownState.getInstance());
	}
	
	public abstract GhostState getScatterState();
	
	public abstract GhostState getChaseState();
	
	public abstract GhostState getEnteringGhostHouseState();
	
	public abstract void setNormalAppearance(int appearanceID);
	
	//TODO: this
	// This function is called if a new move is needed.
	@Override
	public int[] getNextDirection(WorldModel model){
		return states.getFirst().getNextDirection(model,this);
	}

	public Point getCurrentIntersection() {
		return currentIntersection;
	}

	public void setCurrentIntersection(Point currentIntersection) {
		this.currentIntersection.setLocation(currentIntersection);
	}
	
	public void draw(Graphics g) {
		states.getFirst().draw(g, this);
	}
	
	public void collide(WorldModel model, DynamicElement de){
	}

	public LinkedList<GhostState> getStates() {
		return states;
	}
	
	public BufferedImage getAppearance() {
		return appearance;
	}

	public void setAppearance(BufferedImage appearance) {
		this.appearance = appearance;
	}
	
	public boolean isCollisionNecessary(){
		return false;
	}
	
	public void pelletExpired(GhostState state){
		changeState(state,true,false);
	}
	
	public void hitGhostHouseTarget(GhostState state){
		changeState(state,false,true);
	}
	
	public void changeState(GhostState state){
		changeState(state,false,false);
	}
	
	private void changeState(GhostState state, boolean pelletExpired, boolean hitGhostHouseTarget)
	{
			if((states.getFirst() == EatenGhostState.getInstance() && state != getEnteringGhostHouseState())){ 
				return;
			}
			
			//If ghost is vulnerable, dont change state unless it has been eaten or if all pellets have expired
			if(states.getLast() == VulnerableGhostState.getInstance() && state != EatenGhostState.getInstance() && !pelletExpired ){
				return;
			}
			
			if(states.getFirst().getStateType() == StateType.GHOST_HOUSE_STATE ){
				if(state==VulnerableGhostState.getInstance() && states.getFirst() == getEnteringGhostHouseState()){
					return;
				}
				if(!hitGhostHouseTarget)
				{
					return;
				}
			}
			
			//TODO: From state Eaten to Scatter or Chase, ghost breaks out of Eaten state before returning the ghost house. FIX IT.
			
			//System.out.println(this+",B states "+states+" setting reversal");
			
			//if coming out of vulnerable then do not reverse.  If you are eaten, dont reverse. Only reverse if changing to another state that is not noup or chunnel TODO: add this check when implement chunnel and noup
			if(!isNonReversalState() && states.getFirst()!= state)
			{
				Point futureGhost = null;
				if(!model.isPlayableAreaIntersection(getDiscretePosition().x,getDiscretePosition().y))
				{
					futureGhost = this.lookAhead;
				}
				else
				{
					//if at intersection then use the nextAction to get the next block
					futureGhost = ActionUtil.getNextPosition(getDiscretePosition(), ActionUtil.DISCRETE_UNITS, getNextDirection());
	//System.out.println("dis: "+getDiscretePosition());
	//System.out.println("curAct: "+getCurrentAction());
	//System.out.println("nextAct: "+getNextAction());
	//System.out.println("fut: "+futureGhost);
	//System.out.println("futureGhost set");
				}
				setReversalBlock(futureGhost.x, futureGhost.y);
	//System.out.println(this+" setting reversal");
			}
	//else
	//System.out.println(this+" not setting reversal");
			
			if(state == VulnerableGhostState.getInstance())
			{
				if((this.states.getLast() != VulnerableGhostState.getInstance()) && (this.states.getLast() != EatenGhostState.getInstance()))
				{
					this.states.add(state);
					if((states.getFirst() == getScatterState() || (states.getFirst() == getChaseState()))){
						states.removeFirst();
					}
					this.states.getLast().setAppearance(0,this);
				}
			}else if(state == EatenGhostState.getInstance()){
				states.clear();
				states.add(state);
				this.states.getLast().setAppearance(0,this);
			}else {
				states.set(0, state);
				this.states.getFirst().setAppearance(0,this);
			}
//System.out.println(this+",A states "+states+" setting reversal");
	}
	
	private boolean isNonReversalState()
	{
		// Vulnerable is always last in the states, and we check for vulnerable so dont need to check for first if states > 1
		return nonReversalStates.contains(this.states.getLast());
	}
	
	public int[] getReversalBlock() {
		return reversalBlock;
	}
	
	public void setReversalBlock(int x, int y) 
	{
		this.reversalBlock[0] = x;
		this.reversalBlock[1] = y;
	}
	
	//TODO: Add functionality for ghost house, no up, and chunnel states
	public int[] getTarget() 
	{
		//Return the first state since it is the dominate state ( vulnerable is always last)
		return this.states.getFirst().getTarget(model, this);
	}
	
	public Collection<? extends PlayableArea> getPlayableArea(){
		return states.getFirst().getPlayableArea(model);
	}
	
	public void reactToCurrentDiscretePosition(){
		states.getFirst().reactToCurrentDiscretePosition(model, this);
	}
	
	public ArrayList<int[]> validIntersectionDirections(int x, int y) {
		return states.getFirst().validIntersectionDirections(model, x, y);
	}
	
	//DisX and DisY are the current ghosts discrete position
	public boolean getShouldCalculateNextDirection(int disX, int disY, int lookaheadX, int lookaheadY) {
		return states.getFirst().shouldCalculateNextDirection(model, disX, disY,lookaheadX,lookaheadY);
	}
	
	public boolean isIntersection(int x, int y) {
		return states.getFirst().isIntersection(model,x,y);
	}
	
	public int getInHouseStateChanges() {
		return inHouseStateChanges;
	}
	
	public void setInHouseStateChanges(int inHouseStateChanges) {
		this.inHouseStateChanges = inHouseStateChanges;
	}
	
	public void setLeavingTimer(boolean foodTimer) {
		this.leavingTimer = foodTimer;
	}
	
	public boolean getChangeToLeaving() {
		return leavingTimer;
	}
}
